from datetime import datetime, date, timedelta
from itertools import groupby
from collections import defaultdict

from odoo import fields, models, api
from odoo.tools.misc import format_date
from odoo.osv.expression import AND
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT


class MrpTask(models.Model):
    _name = 's.mrp.task'
    _description = 'Mrp Task'

    name = fields.Char('Name')
    priority = fields.Integer('Priority', copy=False, readonly=True)
    production_id = fields.Many2one('mrp.production', string='Production Order', index=True, ondelete='set null')
    product_id = fields.Many2one('product.product', string='Product', required=True)
    station_id = fields.Many2one('work.station', string='Work Station', index=True, required=True)
    planned_completion_date = fields.Date('Planned Completion Date', required=True)  # 计划完成时间
    actual_completion_datetime = fields.Datetime('Actual Completion Datetime')
    actual_completion_date = fields.Date('Actual Completion date')
    qty = fields.Integer('Qty')
    state = fields.Selection([
        ('progress', 'Progress'),
        ('done', 'Done'),
        ('fail', 'Fail'),
        ('cancel', 'Cancel')
    ], copy=False, readonly=True, default='progress', string='Status', index=True)

    station_order_id = fields.Many2one('s.mrp.work.station.order', 'Station Order', copy=False, index=True)
    step = fields.Integer(string='Step', related='station_order_id.step', store=True)

    # 排单的时间点
    plan_modification_date = fields.Datetime(string='Plan modification date', copy=False, readonly=True)

    @api.model
    def save_production_scheduling_qty(self, production_id, station_id, planned_date, qty):
        production_id = self.env['mrp.production'].browse(production_id)
        qty = int(qty)
        vals_list = []
        update_station_order = production_id.s_station_order_ids.filtered(lambda x: x.station_id.id == station_id)
        update_qty = sum(update_station_order.mapped('qty_pnl'))
        if qty >= update_qty:
            for order in production_id.s_station_order_ids.filtered(lambda x: not x.done).sorted(key=lambda x: x.step):
                rec = self.search([
                    ('production_id', '=', production_id.id),
                    ('station_order_id', '=', order.id),
                    ('state', '=', 'progress')
                ], limit=1)
                if not rec:
                    vals_list.append({
                        'production_id': production_id.id,
                        'product_id': production_id.product_id.id,
                        'station_id': order.station_id.id,
                        'planned_completion_date': planned_date,
                        'qty': update_qty
                    })
                else:
                    rec.update({
                        'planned_completion_date': planned_date,
                        'qty': update_qty
                    })
                if order.station_id.id == station_id:
                    break
            self.create(vals_list)
        elif qty < update_qty:
            task_ids = self.search([('station_order_id', 'in', update_station_order.ids), ('state', '=', 'progress')])
            task = task_ids[-1]
            all_task_ids = self.search([
                ('planned_completion_date', '=', task.planned_completion_date),
                ('production_id', '=', task.production_id.id),
                ('step', '>=', task.step),
                ('state', '=', 'progress')
            ])
            x_date = task.planned_completion_date + timedelta(days=1)
            all_task_ids.update({'planned_completion_date': x_date, 'qty': update_qty})

    def scheduling(self, production_id, qty=0, station_id=None, x_date=None, priority=10):
        # vals_list = []
        if not x_date:
            x_date = fields.Date.today()
        for order in production_id.s_station_order_ids.filtered(lambda x: not x.done).sorted(key=lambda x: x.step):
            order_station = order.station_id
            pnl_qty = qty if qty else production_id._get_wip_pnl()
            rec = self.search([('station_order_id', '=', order.id), ('state', '=', 'progress')], limit=1)
            if not rec:
                self.sudo().create({
                    'production_id': production_id.id,
                    'product_id': production_id.product_id.id,
                    'station_id': order_station.id,
                    'planned_completion_date': x_date,
                    'priority': priority,
                    'qty': pnl_qty
                })
            else:
                rec.sudo().update({
                    'planned_completion_date': date,
                    'priority': priority,
                    'qty': pnl_qty
                })

        production_id.s_priority = priority

    def re_schedule(self, x_date):
        ...

    def get_station_date_output(self, station_id, x_date):
        """

        """
        max_output, min_output = station_id.maximum_output, station_id.minimum_output
        records = self.search([
            ('planned_completion_date', '=', x_date),
            ('station_id', '=', station_id.id),
            ('state', '=', 'progress'),
            ('station_order_id', '!=', False)
        ])
        qty = sum(records.mapped('qty'))
        return min_output - qty, max_output - qty

    def get_production_tiers(self, production):
        """
        计算生产单的层级
        """
        tiers = {}
        index = 1
        p_ids = production

        while True:
            child = self.env['mrp.production']
            tiers[index] = p_ids
            for p in p_ids:
                child |= p._get_children().filtered(lambda x: x.state not in ('done', 'cancel'))
            if not child:
                break
            index += 1
            p_ids = child

        return dict(sorted(tiers.items(),key=lambda x: x[0], reverse=True))

    def compute_time_consuming(self, station_id, x_date):
        if not station_id or not station_id.hour_output:
            return 0
        records = self.search([('planned_completion_date', '=', x_date), ('station_id', '=', station_id.id)])
        return sum(records.mapped('qty')) / station_id.hour_output

    def production_scheduling(self, production_ids, priority=10, s_date=None, plan_modification_date=None):
        """
        @param production_ids 生产订单
        @param priority 优先级
        @param s_date 排单的开始时间，为空则为当天
        @param plan_modification_date 排单的时间结点，时间点前的需重排，时间点之后的跳过
        """
        if not s_date:
            s_date = self.get_to_day()
        if not plan_modification_date:
            plan_modification_date = datetime.now()

        for production in production_ids:
            if production.product_id.categ_id.code != 'CHP':
                continue
            production.compute_station_order(all_production=False)
            p_dict = self.get_production_tiers(production)
            for _, p_ids in p_dict.items():
                for p in p_ids:
                    # 没有交货日期，重新计算一遍
                    if not p.s_origin_delivery_date:
                        p.compute_s_origin_delivery_date()
                    x_date = s_date
                    count = 0
                    p.s_station_order_ids.constrains_done()
                    for order in p.s_station_order_ids.filtered(lambda x: not x.done):
                        # 计算一遍，以防止已完成但未更新到
                        if order.done:
                            continue

                        task = self.search([('station_order_id', '=', order.id), ('state', '=', 'progress')], limit=1)
                        # 排单的时间结点，时间点前的需重排，时间点之后的跳过
                        if task and task.plan_modification_date > plan_modification_date:
                            count += 1
                            continue

                        if order.qty_pnl <= 0:
                            continue

                        while True:
                            remaining_min_qty, remaining_max_qty = self.get_station_date_output(order.station_id, x_date)
                            if order.qty_pnl > remaining_max_qty or count == 2:
                                x_date = x_date + timedelta(days=1)
                                count = 0
                                continue

                            if task:
                                task.update({
                                    'planned_completion_date': x_date,
                                    'plan_modification_date': datetime.now()
                                })
                            else:
                                self.create({
                                    'production_id': p.id,
                                    'product_id': p.product_id.id,
                                    'station_id': order.station_id.id,
                                    'planned_completion_date': x_date,
                                    'priority': priority,
                                    'station_order_id': order.id,
                                    'qty': order.qty_pnl,
                                    'plan_modification_date': datetime.now()
                                })
                            count += 1
                            total_time = self.compute_time_consuming(order.station_id, x_date)
                            if total_time > 24:
                                x_date = x_date + timedelta(days=1)
                                count = 0
                            break

    def planned_scheduling(self):
        ...

    def _scheduling_every_day(self):
        now = datetime.now()

        if now.hour < 1 or (now.hour == 1 and now.minute < 30):
            return
        today = self.get_to_day()
        task_ids = self.search([
            ('planned_completion_date', '<', today),
            ('state', '=', 'progress')
        ])
        task_ids.write({'state': 'fail'})

        # self.search([('state', '=', 'progress')]).unlink()

        # 优先排在线的
        wip = self.env['mrp.workorder.sum'].search([])
        wip.sorted(key=lambda x: (-x.production_id.s_priority, x.production_id.level_no), reverse=True)
        for rec in wip:
            if not rec.qty_wip:
                continue
            production = rec.production_id
            rec.production_id.compute_station_order(all_production=False)
            station_order_ids = production.s_station_order_ids.filtered(lambda x: not x.done)
            if not station_order_ids:
                continue
            station_order_ids.sorted(lambda x: x.step)
            order = station_order_ids[0]
            remaining_min_qty, remaining_max_qty = self.get_station_date_output(order.station_id, today)
            if order.qty_pnl > remaining_max_qty:
                continue
            task = self.search([('station_order_id', '=', order.id), ('state', '=', 'progress')], limit=1)
            if task:
                task.update({
                    'planned_completion_date': today,
                    'plan_modification_date': datetime.now()
                })
            else:
                self.create({
                    'production_id': production.id,
                    'product_id': production.product_id.id,
                    'station_id': order.station_id.id,
                    'planned_completion_date': today,
                    'priority': production.s_priority,
                    'station_order_id': order.id,
                    'qty': order.qty_pnl,
                    'plan_modification_date': datetime.now()
                })

        # 查找所有未完成的生产单，重新排程
        production_ids = self.env['mrp.production'].search([('state', 'not in', ('done', 'cancel', 'draft'))])
        production_ids = production_ids.filtered(lambda x: x.product_id.categ_id.code and x.product_id.categ_id.code == 'CHP')
        production_ids = production_ids.sorted(lambda x: (x.s_priority, x.s_origin_delivery_date))
        for production in production_ids:
            self.production_scheduling(production, priority=production.s_priority)

    @api.model
    def get_station_data(self):
        station_ids = self.env['work.station'].search([], order='sequence')

        row_headers = []
        for index, station in enumerate(station_ids, start=1):
            row_headers.append({
                'name': station.name,
                'id': station.id,
            })

        return row_headers

    def check_production_station_editable(self, production_id):
        """
        计算工站是否可编辑
        """
        production_id.ensure_one()
        editable_dict = {}
        if production_id.state in ('done', 'cancel', 'to_close'):
            return editable_dict
        for order in production_id.s_station_order_ids.filtered(lambda x: not x.done).sorted(key=lambda x: x.step):
            editable_dict[order.station_id] = True
        return editable_dict

    @api.model
    def get_to_day(self):
        now = datetime.now()
        if now.hour < 1 or (now.hour == 1 and now.minute < 30):
            now_date = fields.Date.to_date(now - timedelta(days=1))
        else:
            now_date = fields.Date.to_date(now)
        return now_date

    @api.model
    def get_task_data(self, domain=None, s_date=None):
        """
        每个生产单的计划情况
        data = [{production_id, production_name, product_id, product_name, delivery_date, task_data: []}]
        """
        x_date = fields.Date.today()
        if not domain:
            domain = []
        date_list = self.get_date_list(domain)

        if s_date:
            date_str = f'{x_date.year}-{s_date}'
        else:
            s_date = date_list[0]
            date_str = f'{x_date.year}-{s_date}'
        domain = AND([domain, [('planned_completion_date', '=', date_str), ('state', '=', 'progress'), ('station_order_id', '!=', False)]])
        records = self.search(domain, order='production_id')
        station_ids = self.env['work.station'].search([], order='sequence')
        data = []
        total = defaultdict()
        i = 1
        for production_id, g in groupby(records, key=lambda x: x.production_id):
            task_ids = self.concat(*g)
            task_station_vals_list = []
            editable_dict = self.check_production_station_editable(production_id)
            for station in station_ids:
                qty = sum(task_ids.filtered(lambda x: x.station_id == station).mapped('qty'))
                task_station_vals_list.append({
                    'id': i,
                    'station_id': station.id,
                    'pnl': qty,
                    'editable': editable_dict.get(station, False)
                })
                if station in total:
                    total[station] += qty
                else:
                    total[station] = qty
                i += 1
            data.append({
                'priority': production_id.s_priority,
                'production_id': production_id.id,
                'production_name': production_id.name,
                'product_id': production_id.product_id.id,
                'product_name': production_id.product_id.name,
                'delivery_date': format_date(self.env, production_id.s_origin_delivery_date),
                'task_data': task_station_vals_list
            })
        return {
            's_date': s_date,
            'date_list': date_list,
            'lines': sorted(data, key=lambda x: x['priority']),
            'total': list(total.values())
        }

    def get_date_list(self, domain):
        date_list = []
        now_date = self.get_to_day()
        if not domain:
            i = 0
            while i < 10:
                date_list.append(now_date.strftime('%m-%d'))
                now_date = now_date + timedelta(days=1)
                i += 1
        else:
            domain = AND([domain, [('planned_completion_date', '>=', now_date)]])
            records = self.search(domain, order='planned_completion_date')
            i = 0
            d_list = list(set(records.mapped('planned_completion_date')))
            d_list.sort()
            for d in d_list:
                date_list.append(d.strftime('%m-%d'))
                i += 1
                if i == 10:
                    break
        return date_list

    # -------------- 计划排程报表方法 --------------------------------------

    @api.model
    def get_station_line_report_data(self, station_id, s_date):

        x_date = fields.Date.today()
        date_list = self.get_date_list(domain=[])

        if s_date:
            date_str = f'{x_date.year}-{s_date}'
        else:
            s_date = date_list[0]
            date_str = f'{x_date.year}-{s_date}'

        station_data = []
        records = self.search([('state', '=', 'progress'), ('planned_completion_date', '=', date_str)])
        station_ids = self.env['work.station'].search([('disuse', '=', False)], order='sequence')
        for station in station_ids:
            station_data.append({
                'id': station.id,
                'name': station.name,
                'qty': sum(records.filtered(lambda x: x.station_id == station).mapped('qty'))
            })

        task_data = []
        if station_id:
            task_records = self.search([
                ('state', '=', 'progress'),
                ('station_id', '=', station_id),
                ('planned_completion_date', '=', date_str)
            ], order='planned_completion_date')

            for rec in task_records:
                task_data.append({
                    'id': rec.id,
                    'product_name': rec.product_id.name,
                    'production_name': rec.production_id.name,
                    'planned_receiving_date': rec.production_id.s_origin_delivery_date and f'{format_date(self.env, rec.production_id.s_origin_delivery_date)}' or '',
                    'planned_completion_date': format_date(self.env, rec.planned_completion_date),
                    'qty': rec.qty or 0,
                })

        return {
            'date_list': date_list,
            's_date': s_date,
            'station_data': station_data,
            'task_data': task_data
        }
